﻿/*
 * This file is part of MonoStrategy.
 *
 * Copyright (C) 2010-2011 Christoph Husse
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authors: 
 *      # Christoph Husse
 * 
 * Also checkout our homepage: http://monostrategy.codeplex.com/
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Runtime.Serialization.Json;
using System.IO;

namespace MonoStrategy
{
    public static class RaceConfigLoader
    {
        static private Type[] m_BuildingClasses;

        static RaceConfigLoader()
        {
            List<Type> classes = new List<Type>();

            // collect types derived from BaseBuilding
            foreach (var type in typeof(GameMap).Assembly.GetTypes())
            {
                if (type.Namespace != "MonoStrategy")
                    continue;

                if (!typeof(BaseBuilding).IsAssignableFrom(type))
                    continue;

                classes.Add(type);
            }

            m_BuildingClasses = classes.ToArray();
        }

        public static RaceConfig Open(String inXMLPath)
        {
            var reader = new XmlSerializer(typeof(RaceConfig));
            var stream = System.IO.File.OpenRead(inXMLPath);
            RaceConfig result;
            int[] tabIndices = new int[4];
            MemoryStream buffer = new MemoryStream();
            BinaryWriter writer = new BinaryWriter(buffer);

            using (stream)
            {
                result = (RaceConfig)reader.Deserialize(stream);
            }

            writer.Write((Byte)result.Buildables.Count);

            foreach (var buildable in result.Buildables)
            {
                AnimationClass animClass;

                try
                {
                    animClass = AnimationLibrary.Instance.FindClass(buildable.AnimationClass);

                    if (++tabIndices[buildable.TabIndex] > 10)
                        throw new ArgumentException("Too many items in building tab \"" + buildable.TabIndex + "\".");

                    if (!String.IsNullOrEmpty(buildable.ClassString))
                    {
                        foreach (var type in m_BuildingClasses)
                        {
                            if (type.Name == buildable.ClassString)
                            {
                                buildable.ClassType = type;

                                break;
                            }
                        }

                        if (buildable.ClassType == null)
                            throw new ArgumentException("Unknown building class \"" + buildable.ClassString + "\".");
                    }
                    else
                    {
                        buildable.ClassType = typeof(InputOutputBuilding);
                    }

                    buildable.Class = (BuildingClass)Enum.Parse(typeof(BuildingClass), buildable.ClassType.Name);

                    if (buildable.ClassType == typeof(SettlerBuilding))
                    {
                        if ((buildable.SettlerCount <= 0) || (buildable.ResourceStacks.Count != 1) || (buildable.ResourceStacks[0].Direction != ResStackType.Provider))
                            throw new ArgumentException("Given configuration does not describe a house.");
                    }

                    if (typeof(WorkerBuilding).IsAssignableFrom(buildable.ClassType))
                    {
                        // check for spawn point
                        if (!buildable.ResourceStacks.Any(e => e.Type == Resource.Max))
                            throw new ArgumentException("Buildable \"" + buildable.Name + "\" derives from \"WorkerBuilding\" but has no spawn point!");
                    }
                 
                    /*
                     * We need to compute the ZMargin, which indicates the value subtracted from
                     * the usual ZValue of the rendered visual. This is on of the required steps for 3D
                     * emulation. 
                     */
                    buildable.GroundPlane = animClass.GroundPlane;
                    buildable.ReservedPlane = animClass.ReservedPlane;

                    buildable.GridWidth = Math.Max(animClass.GroundPlane.Max(e => e.X + e.Width), animClass.ReservedPlane.Max(e => e.X + e.Width));
                    buildable.GridHeight = Math.Max(animClass.GroundPlane.Max(e => e.Y + e.Height), animClass.ReservedPlane.Max(e => e.Y + e.Height));

                    if (buildable.ResourceStacks.Count != animClass.ResourceStacks.Count)
                        throw new ArgumentException("Configured resource stack count differs from count in animation library.");


                    // postprocess resource stacks
                    int[] availOffsets = new int[(int)Resource.Max + 1];

                    for (int i = 0; i < buildable.ResourceStacks.Count; i++)
                    {
                        ResourceStackConfig stackCfg = buildable.ResourceStacks[i];
                        var availDefs = (from e in animClass.ResourceStacks where e.Resource == stackCfg.Type select e).ToArray();
                        var stackEntry = availDefs[availOffsets[(int)stackCfg.Type]++];

                        stackCfg.MaxStackSize = (stackCfg.MaxStackSize == 0) ? GenericResourceStack.DEFAULT_STACK_SIZE : stackCfg.MaxStackSize;

                        // convert pixel offsets to grid cell space
                        stackCfg.Position = stackEntry.Position;

                        if (stackCfg.TimeOffset == 0)
                        {
                        }

                        // make sure resource spot is in reserved area
                        buildable.ReservedPlane.Add(new Rectangle(stackCfg.Position.X, stackCfg.Position.Y, 1, 1));

                        /*
                         * Configuration time is in millis, but we need cycle time as well as boundary
                         * alignment. See BuildingManager.MillisToCycleBoundary()
                         */
                        stackCfg.TimeOffset = SynchronizedManager.MillisToAlignedCycle(stackCfg.TimeOffset);

                        if (stackCfg.Direction == ResStackType.Query)
                            buildable.ProductionAnimTime = Math.Max(buildable.ProductionAnimTime, stackCfg.TimeOffset);
                    }

                    // save spots for building resources
                    int mid = buildable.GridWidth / 2;

                    buildable.StoneSpot = new Point(mid - 1, buildable.GridHeight);
                    buildable.TimberSpot = new Point(mid + 1, buildable.GridHeight);

                    buildable.ReservedPlane.Add(new Rectangle(buildable.StoneSpot.X, buildable.StoneSpot.Y, 1, 1));
                    buildable.ReservedPlane.Add(new Rectangle(buildable.TimberSpot.X, buildable.TimberSpot.Y, 1, 1));

                    // same as for "stackCfg.TimeOffset"
                    buildable.ProductionTimeMillis = SynchronizedManager.MillisToAlignedCycle(buildable.ProductionTimeMillis);

                    // compute ground grid
                    buildable.GroundGrid = new bool[animClass.GroundPlane.Max(e => e.X + e.Width)][];

                    for (int x = 0, yCount = animClass.GroundPlane.Max(e => e.Y + e.Height); x < buildable.GroundGrid.Length; x++)
                    {
                        buildable.GroundGrid[x] = new bool[yCount];
                    }

                    foreach (var rect in buildable.GroundPlane)
                    {
                        for (int x = rect.Left; x < rect.Right; x++)
                        {
                            for (int y = rect.Top; y < rect.Bottom; y++)
                            {
                                if ((x < 0) || (y < 0))
                                    continue;

                                buildable.GroundGrid[x][y] = true;
                            }
                        }
                    }

                    // there must be at least one line with all bits set to "true"
                    int yGroundLine = -1;

                    for (int y = 0; y < buildable.GroundGrid[0].Length; y++)
                    {
                        if (buildable.GroundGrid.All(e => e[y]))
                        {
                            yGroundLine = y;

                            break;
                        }
                    }

                    if (yGroundLine < 0)
                        throw new ArgumentException("Building \"" + buildable.Name + "\" has no ground line.");

                    // compute reserved grid
                    buildable.ReservedGrid = new bool[animClass.ReservedPlane.Max(e => e.X + e.Width)][];

                    for (int x = 0, yCount = animClass.ReservedPlane.Max(e => e.Y + e.Height); x < buildable.ReservedGrid.Length; x++)
                    {
                        buildable.ReservedGrid[x] = new bool[yCount];
                    }

                    foreach (var rect in buildable.ReservedPlane)
                    {
                        for (int x = rect.Left; x < rect.Right; x++)
                        {
                            for (int y = rect.Top; y < rect.Bottom; y++)
                            {
                                if ((x < 0) || (y < 0))
                                    continue;

                                buildable.ReservedGrid[x][y] = true;
                            }
                        }
                    }

                    // compute constructor spots
                    buildable.ConstructorSpots = new List<Point>();

                    for (int x = 0, height = buildable.GroundGrid[0].Length; x < buildable.GroundGrid.Length; x ++)
                    {
                        // start from ground line and search downwards for first non blocked spot
                        int y = yGroundLine;

                        for (; y < height; y++)
                        {
                            if (!buildable.GroundGrid[x][y])
                            {
                                // we found a reserved cell for constructor
                                break;
                            }
                        }

                        if (y >= buildable.GridHeight)
                            throw new ArgumentException("Building \"" + buildable.Name + "\" is not fully surrounded by reserved cells!");

                        buildable.ConstructorSpots.Add(new Point(x, y));
                    }

                    /////////////////////////////////////////////////////////////////////////////////////////////////
                    /////////////////////////// Binary Serialization...
                    /////////////////////////////////////////////////////////////////////////////////////////////////
                    writer.Write((Byte)buildable.Class);

                    if (buildable.ClassParameter == null)
                    {
                        writer.Write((Byte)0);
                    }
                    else
                    {
                        writer.Write((Byte)Encoding.ASCII.GetByteCount(buildable.ClassParameter));
                        writer.Write(Encoding.ASCII.GetBytes(buildable.ClassParameter));
                    }

                    writer.Write((Byte)buildable.ConstructorSpots.Count);

                    foreach (Point p in buildable.ConstructorSpots) { writer.Write((Byte)p.X); writer.Write((Byte)p.Y); }

                    writer.Write((Byte)buildable.DamageResistance);
                    writer.Write((Byte)buildable.GridHeight);
                    writer.Write((Byte)buildable.GridWidth);

                    writer.Write((Byte)buildable.GroundGrid.Length);
                    writer.Write((Byte)buildable.GroundGrid[0].Length);
                    for (int x = 0; x < buildable.GroundGrid.Length; x++)
                    {
                        for (int y = 0; y < buildable.GroundGrid[0].Length; y++)
                        {
                            writer.Write((Byte)(buildable.GroundGrid[x][y]?1:0));
                        }
                    }

                    writer.Write((Byte)Encoding.ASCII.GetByteCount(buildable.Name));
                    writer.Write(Encoding.ASCII.GetBytes(buildable.Name));
                    writer.Write((Byte)buildable.ProductionAnimTime);
                    writer.Write((Byte)buildable.ProductionTimeMillis);

                    writer.Write((Byte)buildable.ReservedGrid.Length);
                    writer.Write((Byte)buildable.ReservedGrid[0].Length);
                    for (int x = 0; x < buildable.ReservedGrid.Length; x++)
                    {
                        for (int y = 0; y < buildable.ReservedGrid[0].Length; y++)
                        {
                            writer.Write((Byte)(buildable.ReservedGrid[x][y] ? 1 : 0));
                        }
                    }

                    writer.Write((Byte)buildable.ResourceStacks.Count);
                    foreach (var stack in buildable.ResourceStacks)
                    {
                        writer.Write((Byte)stack.CycleCount);
                        writer.Write((Byte)stack.Direction);
                        writer.Write((Byte)stack.MaxStackSize);
                        writer.Write((Byte)stack.Position.X);
                        writer.Write((Byte)stack.Position.Y);
                        writer.Write((Byte)stack.QualityIndex);
                        writer.Write((Byte)stack.TimeOffset);
                        writer.Write((Byte)stack.Type);
                    }
                    
                    writer.Write((Byte)buildable.SettlerCount);
                    writer.Write((Byte)buildable.StoneCount);
                    writer.Write((Byte)buildable.StoneSpot.X);
                    writer.Write((Byte)buildable.StoneSpot.Y);
                    writer.Write((Byte)buildable.TabIndex);
                    writer.Write((Byte)buildable.TimberSpot.X);
                    writer.Write((Byte)buildable.TimberSpot.Y);
                    writer.Write((Byte)buildable.TypeIndex);
                    writer.Write((Byte)buildable.WoodCount);
                    writer.Write((Byte)buildable.Worker);
                }
                catch (Exception e)
                {
                    throw new ArgumentException("Building \"" + buildable.Name + "\" could not be imported (see inner exception for details).", e);
                }
            }

            File.WriteAllBytes(inXMLPath + ".compiled", buffer.ToArray());

            return result;
        }

        private static Point FindNearestFreeSpot(bool[,] reserved, int posX, int posY)
        {
            Point result = new Point(0,0);

            if (WalkResult.NotFound == GridSearch.GridWalkAround(new Point(posX, posY), reserved.GetLength(0), reserved.GetLength(1), (entry) =>
                    {
                        if (reserved[entry.X, entry.Y])
                            return WalkResult.NotFound;

                        result = entry;

                        return WalkResult.Success;
                    }))
                throw new ArgumentException();

            return result;
        }
    }
}
